feat: Added URLs as document objects#1783
feat: Added URLs as document objects#1783AshishViradiya153 wants to merge 3 commits intomfts:mainfrom
Conversation
WalkthroughThese changes introduce comprehensive support for "link"-type documents across the application. This includes the ability to upload external links as documents, render and auto-redirect to links in the viewer, track and analyze link visits, and export link-specific analytics. The backend, frontend, and database schema are all updated to accommodate the new "link" document type, with conditional logic and UI adjustments throughout the codebase. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant UI (AddDocumentModal)
participant API (/documents)
participant DB
User->>UI (AddDocumentModal): Selects "Link" tab and submits URL
UI (AddDocumentModal)->>API (/documents): POST new document (type: "link", url)
API (/documents)->>DB: Create document record (type: "link")
DB-->>API (/documents): Document created
API (/documents)-->>UI (AddDocumentModal): Success response
UI (AddDocumentModal)-->>User: Redirects to new document page
sequenceDiagram
participant User
participant UI (ViewData/LinkViewer)
participant API (/views)
participant DB
User->>UI (ViewData): Opens link-type document
UI (ViewData)->>API (/views): Fetches document version and file
API (/views)->>DB: Query document version (type: "link")
DB-->>API (/views): Returns version with link URL
API (/views)-->>UI (ViewData): Sends link data
UI (ViewData)-->>User: Shows LinkViewer with countdown
Note over UI (ViewData),User: After countdown or button click
UI (ViewData)->>API (/record_link_click): Records redirect event
API (/record_link_click)->>DB: Update view.redirectAt
DB-->>API (/record_link_click): Success
API (/record_link_click)-->>UI (ViewData): Acknowledge
UI (ViewData)-->>User: Redirects to external link
sequenceDiagram
participant User
participant UI (LinkAnalytics)
participant API (/link-analytics)
participant DB
User->>UI (LinkAnalytics): Opens analytics tab for link document
UI (LinkAnalytics)->>API (/link-analytics): GET analytics data
API (/link-analytics)->>DB: Query link views (optionally filter internal users)
DB-->>API (/link-analytics): Returns views data
API (/link-analytics)-->>UI (LinkAnalytics): Sends analytics
UI (LinkAnalytics)-->>User: Displays stats (views, clicks, last redirect)
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~18–25 minutes Complexity label: Moderate
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
npm error Exit handler never called! Note ⚡️ Unit Test Generation is now available in beta!Learn more here, or try it out under "Finishing Touches" below. 📜 Recent review detailsConfiguration used: CodeRabbit UI 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
|
@AshishViradiya153 is attempting to deploy a commit to the mftsio Team on Vercel. A member of the Team first needs to authorize it. |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (4)
components/view/view-data.tsx (1)
113-121: Consider defensive programming for linkUrl propThe conditional rendering logic is well-structured and properly placed in the rendering chain. However, the use of non-null assertion (
viewData.file!) could be risky ifviewData.fileis unexpectedly null or undefined for link-type documents.Consider adding a null check or providing a fallback:
- linkUrl={viewData.file!} + linkUrl={viewData.file || ""}Alternatively, add validation earlier in the conditional:
- ) : viewData.fileType === "link" ? ( + ) : viewData.fileType === "link" && viewData.file ? (pages/api/teams/[teamId]/documents/[id]/link-analytics.ts (1)
72-94: Well-implemented filtering logic with good performance considerations.The code efficiently handles view filtering with proper team member exclusion. Using a Set for email lookups is a good performance optimization.
Consider adding early return for empty views to avoid unnecessary team user queries:
const allViews = document.views ?? []; if (allViews.length === 0) { return res.status(200).json({ views: [], totalViews: 0 }); } let excludedEmails = new Set<string>(); - if (excludeTeamMembers) { + if (excludeTeamMembers && allViews.length > 0) { const teamUsers = await prisma.user.findMany({This avoids the database query when there are no views to filter.
components/documents/add-document-modal.tsx (1)
517-634: Comprehensive link upload implementation with good error handling.The function follows the established pattern from
handleNotionUploadwith proper validation, error handling, analytics tracking, and support for both regular documents and dataroom integration.Consider extracting the URL validation regex to a shared constant to avoid duplication:
+const URL_VALIDATION_REGEX = /^(https?:\/\/)?([a-zA-Z0-9-]+\.){1,}[a-zA-Z]{2,}([a-zA-Z0-9-._~:/?#[\]@!$&'()*+,;=]+)?$/; - const isValidURL = - /^(https?:\/\/)?([a-zA-Z0-9-]+\.){1,}[a-zA-Z]{2,}([a-zA-Z0-9-._~:/?#[\]@!$&'()*+,;=]+)?$/; + const isValidURL = URL_VALIDATION_REGEX;This regex is also used in
handleNotionUpload, so sharing it would improve maintainability.components/view/viewer/link-viewer.tsx (1)
90-99: Good manual link opening implementation with one UX consideration.The function properly clears the auto-redirect timer, tracks the click, and opens the link securely in a new tab. However, redirecting the current window to the Papermark homepage might not be the best UX choice.
Consider alternative approaches for the current window after opening the external link:
- window.location.replace("https://www.papermark.com/home"); + // Option 1: Close the tab if possible + window.close(); + // Option 2: Show a completion message instead of redirecting + // Option 3: Allow user to choose what happens nextThe automatic redirect to the homepage might be unexpected for users who want to keep the original window context.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (20)
app/api/views-dataroom/route.ts(3 hunks)app/api/views/route.ts(3 hunks)components/analytics/links-table.tsx(3 hunks)components/documents/add-document-modal.tsx(4 hunks)components/documents/document-header.tsx(4 hunks)components/documents/link-analytics.tsx(1 hunks)components/links/links-table.tsx(1 hunks)components/links/links-visitors.tsx(2 hunks)components/view/view-data.tsx(2 hunks)components/view/viewer/link-viewer.tsx(1 hunks)components/visitors/visitors-table.tsx(3 hunks)lib/trigger/export-visits.ts(2 hunks)lib/utils/get-file-icon.tsx(2 hunks)pages/api/links/[id]/visits.ts(1 hunks)pages/api/record_link_click.ts(1 hunks)pages/api/teams/[teamId]/documents/[id]/link-analytics.ts(1 hunks)pages/api/teams/[teamId]/documents/[id]/views/index.ts(2 hunks)pages/documents/[id]/index.tsx(4 hunks)prisma/migrations/20250724143745_added_redirect_at/migration.sql(1 hunks)prisma/schema/schema.prisma(2 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/trigger/**/*.ts
📄 CodeRabbit Inference Engine (.cursor/rules/rule-trigger-typescript.mdc)
**/trigger/**/*.ts: You MUST use@trigger.dev/sdk/v3when implementing Trigger.dev tasks.
You MUST NEVER useclient.defineJobin Trigger.dev task files, as it is deprecated and will break the application.
You MUSTexportevery task, including subtasks, in Trigger.dev task files.
If you are able to generate an example payload for a task, do so.
When implementing a Trigger.dev task, always use thetaskfunction from@trigger.dev/sdk/v3and follow the correct pattern for task definition.
When implementing scheduled (cron) tasks, useschedules.taskfrom@trigger.dev/sdk/v3and follow the correct pattern for schedule definition.
When implementing schema-validated tasks, useschemaTaskfrom@trigger.dev/sdk/v3and provide a schema using Zod or another supported library.
When triggering tasks from backend code, use thetasks.trigger,tasks.batchTrigger, ortasks.triggerAndPollmethods from@trigger.dev/sdk/v3and use type-only imports for type safety.
When triggering a task from inside another task, use the correct methods (trigger,batchTrigger,triggerAndWait,batchTriggerAndWait) on the task instance as shown in the guide.
When using metadata in tasks, use themetadataAPI from@trigger.dev/sdk/v3only inside run functions or task lifecycle hooks.
When using idempotency, use theidempotencyKeysAPI from@trigger.dev/sdk/v3and provide anidempotencyKeywhen triggering tasks.
When logging inside tasks, use theloggerAPI from@trigger.dev/sdk/v3and provide a message and a key-value object.
Files:
lib/trigger/export-visits.ts
🧬 Code Graph Analysis (6)
components/links/links-visitors.tsx (2)
components/ui/tooltip.tsx (1)
BadgeTooltip(53-107)lib/utils.ts (2)
timeAgo(153-171)durationFormat(173-183)
components/visitors/visitors-table.tsx (4)
components/view/viewer/dataroom-viewer.tsx (1)
DocumentVersion(47-54)components/ui/table.tsx (1)
TableHead(114-114)components/ui/tooltip.tsx (1)
BadgeTooltip(53-107)lib/utils.ts (2)
timeAgo(153-171)durationFormat(173-183)
components/view/view-data.tsx (1)
components/view/viewer/link-viewer.tsx (1)
LinkViewer(26-182)
lib/trigger/export-visits.ts (1)
lib/tinybird/pipes.ts (3)
getViewPageDuration(24-36)getViewUserAgent(95-107)getViewUserAgent_v2(79-93)
components/documents/link-analytics.tsx (3)
components/view/viewer/dataroom-viewer.tsx (1)
DocumentVersion(47-54)lib/utils.ts (1)
fetcher(36-50)components/ui/card.tsx (2)
Card(80-80)CardContent(85-85)
components/documents/add-document-modal.tsx (2)
components/ui/card.tsx (5)
Card(80-80)CardHeader(81-81)CardTitle(83-83)CardDescription(84-84)CardContent(85-85)components/ui/button.tsx (1)
Button(71-71)
🔇 Additional comments (52)
prisma/schema/schema.prisma (1)
286-288: LGTM: Well-structured schema addition for link tracking.The optional
redirectAtfield is properly typed and documented. The placement within the View model is logical and aligns with the PR's objective to support link-type documents.prisma/migrations/20250724143745_added_redirect_at/migration.sql (1)
1-2: LGTM: Correct migration for schema update.The migration properly adds the
redirectAtcolumn with appropriate timestamp precision and nullable constraint, matching the schema definition.components/links/links-table.tsx (1)
899-899: LGTM: Proper conditional prop for link-type documents.The
isLinkprop is correctly set based on bothtargetTypeandprimaryVersion?.type, ensuring proper identification of link documents for specialized rendering.lib/utils/get-file-icon.tsx (2)
1-1: LGTM: Proper import addition for link icon.The
LinkIconimport is correctly added to support the new link file type case.
67-68: LGTM: Consistent file type handling for links.The new "link" case follows the established pattern and properly returns the
LinkIconwith the providedclassName.components/analytics/links-table.tsx (4)
25-25: LGTM: Icon import updated for consistency.The change from
Link2IcontoLinkIconaligns with the icon usage in other components.
30-34: LGTM: Improved import organization.Consolidating utility imports together improves code organization and readability.
318-318: LGTM: CSS class reordering.The class order change doesn't affect functionality and may follow a consistent style guide.
368-368: LGTM: Consistent icon usage.The JSX update to use
LinkIconmatches the import change and maintains consistency across components.components/view/view-data.tsx (1)
28-28: LGTM: Clean import statementThe import follows the established pattern and correctly references the new LinkViewer component.
pages/documents/[id]/index.tsx (4)
15-15: LGTM: Clean import statementThe import follows the established pattern and correctly references the new LinkAnalytics component.
93-99: LGTM: Logical exclusion of link documents from statsThe condition correctly excludes both video and link document types from the general StatsComponent, which makes sense as these document types have specialized analytics components.
110-116: LGTM: Consistent implementation patternThe LinkAnalytics conditional rendering follows the same pattern as VideoAnalytics and properly passes all required props. The placement in the component hierarchy is logical.
130-130: LGTM: Consistent prop patternThe addition of the
isLinkprop follows the same pattern as the existingisVideoprop and enables appropriate handling of link-type documents in the visitors table.pages/api/teams/[teamId]/documents/[id]/views/index.ts (2)
170-188: LGTM: Well-structured function for link document viewsThe
getLinkViewsfunction follows the established pattern of other view processing functions and appropriately handles link documents with fixed values that make sense for this document type:
- Empty duration data (links don't have page-based viewing)
- Zero total duration (no time-based metrics)
- 100% completion rate (accessing a link is considered complete)
The version matching logic is consistent with existing implementations.
319-320: LGTM: Logical conditional placementThe conditional for link documents is appropriately placed in the type-specific handling chain and correctly calls
getLinkViewswith the proper parameters.app/api/views/route.ts (2)
101-112: LGTM: Efficient nested query structureThe enhanced Prisma query correctly fetches the document version file data needed for link document handling. The nested structure with proper filtering and selective field retrieval is efficient and well-structured.
656-657: LGTM: Correct inclusion of link typeThe addition of "link" type to the conditional check ensures that link documents properly return their file data in the API response, which is necessary for the LinkViewer component to function correctly.
pages/api/record_link_click.ts (1)
1-55: Enhance rate limiting and duplicate-click prevention in record_link_click.tsThe endpoint is currently public-by-design for click tracking, but to mitigate abuse you can:
• Add per-IP rate limiting with the existing
ratelimithelper
• Extract the client IP viaipAddress(req)from@vercel/functions
• Short-circuit whenview.redirectAtis already set to avoid duplicate updatesExample diff:
import { NextApiRequest, NextApiResponse } from "next"; import { z } from "zod"; import prisma from "@/lib/prisma"; import { log } from "@/lib/utils"; +import { ratelimit } from "@/lib/redis"; +import { ipAddress } from "@vercel/functions"; export default async function handle( req: NextApiRequest, res: NextApiResponse, ) { if (req.method !== "POST") { res.status(405).json({ message: "Method Not Allowed" }); return; } + // Rate limit by IP + const ip = ipAddress(req); + const { success } = await ratelimit(10, "1 m").limit(`record-click:${ip}`); + if (!success) { + return res.status(429).json({ message: "Too many requests" }); + } const { viewId, linkId } = req.body as { viewId: string; linkId: string; }; const result = bodyValidation.safeParse({ viewId, linkId }); if (!result.success) { return res.status(400).json({ error: `Invalid body: ${result.error.message}` }); } try { const link = await prisma.link.findUnique({ where: { id: linkId } }); if (!link) return res.status(404).json({ message: "Link not found" }); const view = await prisma.view.findUnique({ where: { id: viewId }, }); if (!view || view.linkId !== linkId) { return res.status(404).json({ message: "View not found" }); } + // Prevent duplicate recordings + if (view.redirectAt) { + return res + .status(200) + .json({ message: "Click already recorded", view }); + } const updated = await prisma.view.update({ where: { id: viewId }, data: { redirectAt: new Date().toISOString() }, }); res.status(200).json({ message: "Link click recorded", view: updated }); } catch (error) { log({ message: `Failed to record link click for ${linkId}.\n\n ${error}`, type: "error", mention: true, }); res.status(500).json({ message: (error as Error).message }); } }Please confirm whether this endpoint should remain publicly accessible for tracking (no auth) or if an authentication layer is required.
components/links/links-visitors.tsx (3)
2-2: LGTM! Import additions are appropriate.The new imports for
durationFormat,timeAgo, andBadgeTooltipare necessary for the conditional rendering functionality.Also applies to: 7-7
13-13: LGTM! Prop addition follows best practices.The
isLinkprop with a default value offalseensures backward compatibility while enabling the new link-specific functionality.Also applies to: 17-17
42-57:redirectAtfield is correctly typed and availableVerified that:
- The Prisma schema defines
redirectAtas an optionalDateTime?(seeprisma/schema/schema.prisma)- Migration
20250724143745_added_redirect_atadds the column to theViewtablepages/api/record_link_click.tsupdatesredirectAton link clicks- Front-end components (
components/links/links-visitors.tsxandcomponents/visitors/visitors-table.tsx) referenceview.redirectAtwithout any TypeScript errorsThe conditional rendering for link vs non-link views is clean and the tooltip behavior is spot on. Great work!
components/visitors/visitors-table.tsx (2)
67-67: LGTM! Consistent prop pattern.The
isLinkprop follows the same pattern as the LinksVisitors component, maintaining consistency across the codebase.Also applies to: 71-71
143-145: Excellent UX improvement with conditional table header.The header clearly indicates whether users are viewing "Redirect Time" or "Visit Duration", providing important context for the data displayed.
pages/api/teams/[teamId]/documents/[id]/link-analytics.ts (3)
1-12: LGTM! Clean imports and well-defined interface.The API response interface is properly typed and the imports are appropriate for the functionality.
18-28: LGTM! Proper method validation and authentication.The authentication flow follows Next.js best practices with appropriate error responses.
37-65: Excellent access control and validation logic.The implementation properly:
- Verifies team membership through the Prisma query
- Validates document existence and access
- Ensures the document is of type "link"
- Returns appropriate HTTP status codes
app/api/views-dataroom/route.ts (3)
123-134: LGTM! Nested selection enables access to linked document files.The nested query structure properly retrieves the document version file data needed for link-type documents.
836-840: Proper handling of link-type document versions.The logic correctly retrieves the file from the linked document's first version with appropriate null checks.
937-938: LGTM! Condition update includes link types appropriately.Adding "link" to the file return condition ensures link-type documents return their file data consistently with other supported types.
lib/trigger/export-visits.ts (2)
198-198: LGTM! Necessary query addition for document type.Adding the document type to the query enables the conditional processing logic for link documents.
292-398: Excellent conditional processing for link vs non-link documents.The implementation appropriately handles the fundamental differences:
For link documents:
- Avoids unnecessary Tinybird API calls (performance benefit)
- Uses sensible placeholder values (0 duration, 100% completion)
- Maintains consistent CSV structure
For non-link documents:
- Preserves existing rate-limited API behavior
- Calculates actual duration and completion metrics
The logic is clean, well-structured, and maintains backward compatibility while adding link support.
components/documents/document-header.tsx (4)
540-542: Good refactoring to improve readability and exclude link documents from orientation changes.The refactoring from explicit inequality comparisons to array membership checks using
.includes()makes the code more readable and maintainable. Excluding "link" documents from orientation changes is logical since external links don't have document orientation.
565-565: Appropriate exclusion of link documents from version uploads.Correctly excludes "link" documents from the "Upload new version" functionality, which makes sense since external links cannot have file versions uploaded to them.
728-754: Correctly excludes link documents from download-only toggle.The "Set download only/viewable" option is appropriately excluded for "link" documents since external links inherently redirect users to external sites and cannot be restricted to download-only mode.
792-801: Properly excludes link documents from download functionality.Excluding "link" documents from the "Download latest version" option is correct since external links cannot be downloaded as files. The refactoring also improves code readability.
components/documents/add-document-modal.tsx (5)
75-75: State variable addition follows existing patterns.The
linkUrlstate variable is consistent with the existingnotionLinkpattern and follows React best practices.
507-515: Well-implemented URL-to-filename helper function.Good implementation with proper error handling, sensible fallback behavior, and clean URL parsing logic that removes common prefixes like "www.".
639-639: Proper state cleanup for link URL.Correctly adds
linkUrlstate reset to the modal state clearing function, maintaining consistency with other state variables.
679-682: Clean tab list expansion for link functionality.Properly updates the tab grid layout and adds the new "Link" tab trigger with appropriate styling and labeling.
814-861: Well-structured Link tab implementation.The new tab content follows established UI patterns with proper form structure, semantic HTML (
type="url"), appropriate loading states, and helpful user guidance. The implementation is consistent with existing document and notion tabs.components/view/viewer/link-viewer.tsx (4)
17-24: Well-implemented domain extraction with proper error handling.Good performance optimization by placing the function outside the component scope. The error handling and fallback behavior are appropriate for URL parsing.
48-61: Excellent use of sendBeacon for reliable link click tracking.Using
navigator.sendBeacon()is the correct approach for tracking as it ensures the request is sent even if the page unloads immediately. The conditional checks and error handling are well implemented.
63-68: Robust redirect implementation with duplicate protection.The use of
useCallback, duplicate redirect protection viahasRedirectedref, andwindow.location.replace()creates a reliable redirect mechanism that prevents issues with browser navigation.
70-88: Well-implemented auto-redirect with proper cleanup.The timer implementation correctly handles component unmounting with the
isMountedflag, provides user feedback with countdown updates, and properly cleans up both the timeout and interval to prevent memory leaks.components/documents/link-analytics.tsx (3)
51-56: Clean implementation of toggle functionality with URL sync.Properly synchronizes the toggle state with URL parameters, enabling bookmarkable and shareable links. The use of
URLSearchParamsensures correct query parameter handling.
112-122: Robust analytics calculations with proper null handling.The calculations correctly handle the
redirectAtfield specific to link documents, with appropriate filtering, sorting, and type checking for edge cases.
124-142: Well-structured statistics with appropriate formatting.The statistics are properly configured for link documents with human-readable time formatting and appropriate handling of cases where no redirections have occurred.
pages/api/links/[id]/visits.ts (4)
18-21: Good early method validation with proper HTTP response.Adding upfront method validation with appropriate headers and status codes is a good practice that prevents unnecessary processing.
32-66: Improved query structure with proper validation.Good additions including the document
typefield in the query, explicit document existence checking, and appropriate 404 responses for missing documents.
96-131: Appropriate conditional handling for link documents.The conditional logic correctly handles link documents by providing appropriate placeholder values (empty duration data, 100% completion rate) while maintaining existing functionality for other document types.
133-140: Consistent response structure and error handling.The response maintains the expected data structure while preserving robust error handling and logging patterns.
Summary by CodeRabbit
New Features
Improvements
Bug Fixes
Database